// ============================================================================
// File: pkg/snapshotter/service.go
// ============================================================================
package snapshotter

import (
	"context"

	snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
	"github.com/containerd/containerd/api/types"
	empty "github.com/containerd/containerd/protobuf/types"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/mount"
	"github.com/containerd/containerd/snapshots"
)

type Service struct {
	snapshotsapi.UnimplementedSnapshotsServer
	snapshotter snapshots.Snapshotter
}

func NewService(s snapshots.Snapshotter) *Service {
	return &Service{snapshotter: s}
}

func (s *Service) Prepare(ctx context.Context, req *snapshotsapi.PrepareSnapshotRequest) (*snapshotsapi.PrepareSnapshotResponse, error) {
	mounts, err := s.snapshotter.Prepare(ctx, req.Key, req.Parent)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}

	return &snapshotsapi.PrepareSnapshotResponse{
		Mounts: mountsToProto(mounts),
	}, nil
}

func (s *Service) View(ctx context.Context, req *snapshotsapi.ViewSnapshotRequest) (*snapshotsapi.ViewSnapshotResponse, error) {
	mounts, err := s.snapshotter.View(ctx, req.Key, req.Parent)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}

	return &snapshotsapi.ViewSnapshotResponse{
		Mounts: mountsToProto(mounts),
	}, nil
}

func (s *Service) Mounts(ctx context.Context, req *snapshotsapi.MountsRequest) (*snapshotsapi.MountsResponse, error) {
	mounts, err := s.snapshotter.Mounts(ctx, req.Key)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}

	return &snapshotsapi.MountsResponse{
		Mounts: mountsToProto(mounts),
	}, nil
}

func (s *Service) Commit(ctx context.Context, req *snapshotsapi.CommitSnapshotRequest) (*empty.Empty, error) {
	if err := s.snapshotter.Commit(ctx, req.Name, req.Key); err != nil {
		return nil, errdefs.ToGRPC(err)
	}
	return &empty.Empty{}, nil
}

func (s *Service) Remove(ctx context.Context, req *snapshotsapi.RemoveSnapshotRequest) (*empty.Empty, error) {
	if err := s.snapshotter.Remove(ctx, req.Key); err != nil {
		return nil, errdefs.ToGRPC(err)
	}
	return &empty.Empty{}, nil
}

func (s *Service) Stat(ctx context.Context, req *snapshotsapi.StatSnapshotRequest) (*snapshotsapi.StatSnapshotResponse, error) {
	info, err := s.snapshotter.Stat(ctx, req.Key)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}

	return &snapshotsapi.StatSnapshotResponse{
		Info: infoToProto(info),
	}, nil
}

func (s *Service) Update(ctx context.Context, req *snapshotsapi.UpdateSnapshotRequest) (*snapshotsapi.UpdateSnapshotResponse, error) {
	info, err := s.snapshotter.Update(ctx, infoFromProto(req.Info), req.UpdateMask.GetPaths()...)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}

	return &snapshotsapi.UpdateSnapshotResponse{
		Info: infoToProto(info),
	}, nil
}

func (s *Service) List(req *snapshotsapi.ListSnapshotsRequest, srv snapshotsapi.Snapshots_ListServer) error {
	return s.snapshotter.Walk(srv.Context(), func(ctx context.Context, info snapshots.Info) error {
		return srv.Send(&snapshotsapi.ListSnapshotsResponse{
			Info: []*snapshotsapi.Info{infoToProto(info)},
		})
	})
}

func (s *Service) Usage(ctx context.Context, req *snapshotsapi.UsageRequest) (*snapshotsapi.UsageResponse, error) {
	usage, err := s.snapshotter.Usage(ctx, req.Key)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}

	return &snapshotsapi.UsageResponse{
		Size:   usage.Size,
		Inodes: usage.Inodes,
	}, nil
}

func (s *Service) Cleanup(ctx context.Context, req *snapshotsapi.CleanupRequest) (*empty.Empty, error) {
	return &empty.Empty{}, nil
}

func mountsToProto(mounts []mount.Mount) []*types.Mount {
	var out []*types.Mount
	for _, m := range mounts {
		out = append(out, &types.Mount{
			Type:    m.Type,
			Source:  m.Source,
			Options: m.Options,
		})
	}
	return out
}

func infoToProto(info snapshots.Info) *snapshotsapi.Info {
	return &snapshotsapi.Info{
		Name:   info.Name,
		Parent: info.Parent,
		Kind:   snapshotsapi.Kind(info.Kind),
		Labels: info.Labels,
	}
}

func infoFromProto(info *snapshotsapi.Info) snapshots.Info {
	return snapshots.Info{
		Name:   info.Name,
		Parent: info.Parent,
		Kind:   snapshots.Kind(info.Kind),
		Labels: info.Labels,
	}
}